﻿//checked
using System.Collections.Generic;
using System.IO;
using System.Linq;
using net.entity.framework.common;

namespace net.entity.framework.query {
    public sealed class CompiledQueryState {
        #region Members

        private readonly QueryState queryState;
        private readonly List<AliasInfo> aliasesInUse = new List<AliasInfo>();
        private readonly Dictionary<AliasInfo, string> aliasKeyMap = new Dictionary<AliasInfo, string>();

        #endregion

        #region Properties

        public QueryState QueryState {
            get { return queryState; }
        }

        public QueryRoot[] SelectRoots {
            get { return queryState.SelectRoots; }
        }

        public bool Distinct {
            get { return queryState.Distinct; }
        }

        public int Top {
            get { return queryState.Top; }
        }

        public QueryClause WhereClause {
            get { return queryState.WhereClause; }
        }

        public Terminal[] GroupByRoots {
            get { return queryState.GroupByRoots; }
        }

        public QueryClause HavingClause {
            get { return queryState.HavingClause; }
        }

        public QueryBuilder[] NestedFroms {
            get { return queryState.NestedFroms; }
        }

        public Terminal[] OrderByRoots {
            get { return queryState.OrderByRoots; }
        }

        public bool ContainsAliases {
            get { return !aliasesInUse.IsEmpty(); }
        }

        #endregion

        public CompiledQueryState(QueryState queryState) {
            this.queryState = queryState;
        }

        public void Compile() {
            foreach (var aliasFinder in aliasesInUse) {
                CreateAlias(aliasFinder);
            }
        }

        public void EmitFromClause(TextWriter writer) {
            var baseAliases = aliasesInUse.Where(x => ReferenceEquals(x.JoinAliasInfo, null)).ToList();
            var joinedAliases = aliasesInUse.Except(baseAliases).ToList();
            var index = 0;
            foreach (var baseAlias in baseAliases) {
                index++;
                //emit the base alias
                var baseAliasName = LookupAlias(baseAlias);
                writer.Write("\t{0}.dbo.{1} [{2}]", baseAlias.Catalog, baseAlias.TableName,
                             baseAliasName);

                //emit the joined alias
                var matchedJoinedAliases =
                    joinedAliases.Where(x => AliasInfo.RootObjectTypeComparer(x, baseAlias));
                foreach (var joinedAlias in matchedJoinedAliases) {
                    var joinedAliasName = LookupAlias(joinedAlias);
                    writer.Write("\n\tJOIN {0}.dbo.{1} [{2}] ON {2}.[id] = {3}.[{4}_id]",
                                 joinedAlias.Catalog, joinedAlias.TableName, joinedAliasName, baseAliasName,
                                 joinedAlias.ColumnPrefix);
                }
                if (index < baseAliases.Count) {
                    writer.Write(",\n");
                }
            }
        }

        private void CreateAlias(AliasInfo aliasInfo) {
            aliasKeyMap[aliasInfo] = aliasKeyMap.LookupOrDefault(aliasInfo, "t" + aliasKeyMap.Count);
        }

        public void LookupOrAddAlias(AliasInfo aliasInfo) {
            var exists = aliasesInUse.Contains(aliasInfo, AliasInfo.EqualityComparer.Default);
            if (!exists) {
                aliasesInUse.Add(aliasInfo);
            }
        }

        public string LookupAlias(AliasInfo aliasInfo) {
            return aliasKeyMap.Lookup(aliasInfo, true);
        }
    }
}